import {
	stat,
} from 'node:fs/promises';
import {
	readFileSync,
} from 'node:fs';
import {
	join,
} from 'node:path';
import TerserPlugin from 'terser-webpack-plugin';

//region extend
class CopyReplace{
	value;
	constructor(value){
		this.value=value;
	}
}
function copyReplace(value){
	return new CopyReplace(value);
}
class CopyCover{
	value;
	constructor(value){
		this.value=value;
	}
}
function copyCover(value){
	return new CopyCover(value);
}
class CopyCustom{
	value;
	constructor(value){
		this.value=value;
	}
}
function copyCustom(value){
	return new CopyCustom(value);
}
function isBaseType(val){
	return val==null||
		typeof val==='boolean'||
		typeof val==='number'||
		typeof val==='string'||
		typeof val==='function'||
		typeof val==='bigint'||
		typeof val==='symbol';
}
function extend(target,...srcs){
	if(isBaseType(target)){
		throw new TypeError('target必须是对象或数组!');
	}
	const isArray=Array.isArray(target);
	for(let i=0;i<srcs.length;++i){
		const src=srcs[i];
		/**
		 * 当以下情况时忽略拷贝:
		 * 1.被拷贝对象为基本类型时
		 * 2.被拷贝对象和目标对象,类型不相同时
		 */
		if(
			isBaseType(src)||
			(
				(Array.isArray(src)||src instanceof CopyReplace)!==isArray
			)
		) continue;
		copy(target,src);
	}
	return target;
}
function copy(dest,src){
	if(Array.isArray(src)){
		const len=dest.length;
		//数组新增模式
		for(let i=0;i<src.length;++i){
			_copyInner(dest,src[i],len+i);
		}
	}else if(src.constructor===CopyReplace){
		//数组替换模式
		const array=src.value;
		for(let i=0;i<array.length;++i){
			_copyInner(dest,array[i],i);
		}
	}else{
		// dest,src都是对象
		for(const key of Object.keys(src)){
			_copyInner(dest,src[key],key);
		}
	}
}
function _copyInner(dest,value,key){
	let valConstructor;
	if(isBaseType(value)){
		dest[key]=value;
	}else if(value instanceof Date){
		dest[key]=new Date(value);
	}else if(value instanceof RegExp){
		const regexp=new RegExp(value.source,/\w*$/.exec(value));
		regexp.lastIndex=value.lastIndex;
		dest[key]=regexp;
	}else if(value instanceof Set||value instanceof Map){
		dest[key]=new value.constructor(value);
	}else if((valConstructor=value.constructor)===CopyCover){
		dest[key]=value.value;
	}else if(valConstructor===CopyCustom){
		dest[key]=value.value(dest[key]);
	}else{
		// 被拷贝值为数组或对象
		const oldValue=dest[key];
		if(
			isBaseType(oldValue)||
			(
				(Array.isArray(value)||valConstructor===CopyReplace)!==Array.isArray(oldValue)
			)
		){
			/**
			 * 当以下情况时丢弃原有值:
			 * 1.原有值为基本类型
			 * 2.原有值和被拷贝值,类型不相同时
			 */
			dest[key]=valConstructor===CopyReplace?[]:new value.constructor();
		}
		return copy(dest[key],value);
	}
}
//endregion
//region pad
function pad(value,length,option){
	const {char='0',start=true}=option||{};
	const val=''+value;
	if(val.length>=length){
		return val;
	}else{
		let pad=char;
		for(let i=length-val.length-1;i>0;--i){
			pad+=char;
		}
		return start?pad+val:val+pad;
	}
}
//endregion
//region replaceString
function replaceString(src,search,replace){
	let pos=0;
	let next=src.indexOf(search);
	let str='';
	while(next!==-1){
		str+=src.slice(pos,next)+replace;
		pos=next+search.length;
		next=src.indexOf(search,pos);
	}
	return str+src.slice(pos);
}
//endregion
//region 日期工具基础参数
const MILLISECONDS_IN_DAY=86400000;
const MILLISECONDS_IN_HOUR=3600000;
const MILLISECONDS_IN_MINUTE=60000;
const TIMEZONE_OFFSET_IN_MINUTE=new Date().getTimezoneOffset();
const TIMEZONE_OFFSET=TIMEZONE_OFFSET_IN_MINUTE*MILLISECONDS_IN_MINUTE;
const tokenRegex=/'(?:[^']|'')+'|yy(?:yy)?|M{1,4}|d{1,2}|D{1,2}|E{1,3}|H{1,2}|h{1,2}|m{1,2}|s{1,2}|S{1,3}|Z{1,2}|Q{1,2}|k{1,2}|K{1,2}|e|a/g;
const defaultDateLocale={
	weeks:['星期日','星期一','星期二','星期三','星期四','星期五','星期六'],
	weeksShort:['周日','周一','周二','周三','周四','周五','周六'],
	months:['一月','二月','三月','四月','五月','六月','七月','八月','九月','十月','十一月','十二月'],
	monthsShort:['一月','二月','三月','四月','五月','六月','七月','八月','九月','十月','十一月','十二月'],
	quarters:['春','夏','秋','冬'],
	am:'上午',
	pm:'下午',
};
//endregion
//region startOfDate
function startOfDate(date,unit,self){
	const time=self?date:new Date(date);
	if(unit==='year'){
		time.setMonth(0,1);
		time.setHours(0,0,0,0);
	}else if(unit==='month'){
		time.setDate(1);
		time.setHours(0,0,0,0);
	}else if(unit==='day'){
		time.setHours(0,0,0,0);
	}else if(unit==='hour'){
		time.setMinutes(0,0,0);
	}else if(unit==='minute'){
		time.setSeconds(0,0);
	}else{
		time.setMilliseconds(0);
	}
	return time;
}
//endregion
//region endOfDate
/**
 * 获取一个日期指定单位的结束日期
 * @param date		日期
 * @param unit		时间单位
 * @param [self]	是否修改传入的日期
 * @return 日期
 */
function endOfDate(date, unit, self){
	const time = self ? date : new Date(date);
	if(unit === 'year'){
		time.setFullYear(date.getFullYear() + 1, 0, 0);
		time.setHours(23, 59, 59, 999);
	}else if(unit === 'month'){
		time.setMonth(date.getMonth() + 1, 0);
		time.setHours(23, 59, 59, 999);
	}else if(unit === 'day'){
		time.setHours(23, 59, 59, 999);
	}else if(unit === 'hour'){
		time.setMinutes(59, 59, 999);
	}else if(unit === 'minute'){
		time.setSeconds(59, 999);
	}else{
		time.setMilliseconds(999);
	}
	return time;
}
//endregion
//region getDateDiff
function _getDiff(date,subtract,interval){
	return Math.floor((date-TIMEZONE_OFFSET)/interval)
		- Math.floor((subtract-TIMEZONE_OFFSET)/interval);
}
function getDateDiff(date,subtract,unit){
	if(unit==='year'){
		return date.getFullYear()-subtract.getFullYear();
	}else if(unit==='month'){
		return (date.getFullYear()-subtract.getFullYear())*12+date.getMonth()-subtract.getMonth();
	}else if(unit==='day'){
		return _getDiff(date.getTime(),subtract.getTime(),MILLISECONDS_IN_DAY);
	}else if(unit==='hour'){
		return _getDiff(date.getTime(),subtract.getTime(),MILLISECONDS_IN_HOUR);
	}else if(unit==='minute'){
		return _getDiff(date.getTime(),subtract.getTime(),MILLISECONDS_IN_MINUTE);
	}else{
		return _getDiff(date.getTime(),subtract.getTime(),1000);
	}
}
//endregion
//region daysInMonth
function daysInMonth(dateOrYear, selfOrMonth){
	if(typeof dateOrYear === 'number'){
		return new Date(dateOrYear, selfOrMonth, 0).getDate();
	}else if(selfOrMonth){
		dateOrYear.setMonth(dateOrYear.getMonth() + 1, 0);
		return dateOrYear.getDate();
	}else{
		return new Date(dateOrYear.getFullYear(), dateOrYear.getMonth() + 1, 0).getDate();
	}
}
//endregion
//region getDayOfYear
function getDayOfYear(dateOrYear, selfOrMonth, day){
	const isNum = typeof dateOrYear === 'number';
	if(isNum){
		dateOrYear = new Date(dateOrYear, selfOrMonth - 1, day);
	}
	return _getDiff(
			dateOrYear.getTime(),
			startOfDate(dateOrYear, 'year', isNum || selfOrMonth).getTime(),
			MILLISECONDS_IN_DAY,
	) + 1;
}
//endregion
//region 时间的Formatter
const timeFormatter={
	yy(date){
		const y=date.getFullYear()%100;
		return pad(y,2);
	},
	yyyy(date){
		return date.getFullYear();
	},
	Q(date){
		return Math.ceil((date.getMonth()+1)/3);
	},
	QQ(date,dateLocale){
		return dateLocale.quarters[Math.floor(date.getMonth()/3)];
	},
	M(date){
		return date.getMonth()+1;
	},
	MM(date){
		return pad(date.getMonth()+1,2);
	},
	MMM(date,dateLocale){
		return dateLocale.monthsShort[date.getMonth()];
	},
	MMMM(date,dateLocale){
		return dateLocale.months[date.getMonth()];
	},
	d(date){
		return date.getDate();
	},
	dd(date){
		return pad(date.getDate(),2);
	},
	D(date){
		return getDayOfYear(date);
	},
	DD(date){
		return pad(getDayOfYear(date),3);
	},
	e(date){
		return date.getDay();
	},
	E(date){
		return date.getDay()||7;
	},
	EE(date,dateLocale){
		return dateLocale.weeksShort[date.getDay()];
	},
	EEE(date,dateLocale){
		return dateLocale.weeks[date.getDay()];
	},
	H(date){
		return date.getHours();
	},
	HH(date){
		return pad(date.getHours(),2);
	},
	h(date){
		return date.getHours()%12||12;
	},
	hh(date){
		return pad(date.getHours()%12||12,2);
	},
	K(date){
		return date.getHours()%12;
	},
	KK(date){
		return pad(date.getHours()%12,2);
	},
	k(date){
		return date.getHours()||24;
	},
	kk(date){
		return pad(date.getHours()||24,2);
	},
	m(date){
		return date.getMinutes();
	},
	mm(date){
		return pad(date.getMinutes(),2);
	},
	s(date){
		return date.getSeconds();
	},
	ss(date){
		return pad(date.getSeconds(),2);
	},
	S(date){
		return Math.floor(date.getMilliseconds()/100);
	},
	SS(date){
		return pad(Math.floor(date.getMilliseconds()/10),2);
	},
	SSS(date){
		return pad(date.getMilliseconds(),3);
	},
	a(date,dateLocale){
		return date.getHours()<12?dateLocale.am:dateLocale.pm;
	},
	Z(date,dateLocale,timezoneOffset){
		return formatTimezone(':',timezoneOffset);
	},
	ZZ(date,dateLocale,timezoneOffset){
		return formatTimezone('',timezoneOffset);
	},
};
//endregion
//region 格式化时区
function formatTimezone(separator,timezoneOffset){
	const offset=timezoneOffset!=null?timezoneOffset:TIMEZONE_OFFSET_IN_MINUTE;
	const sign=offset>0?'-':'+';
	const absOffset=Math.abs(offset);
	const hours=Math.floor(absOffset/60);
	const minutes=absOffset%60;
	return sign+pad(hours,2)+separator+pad(minutes,2);
}
//endregion
//region dateFormat
const DATEFORMAT_CACHE_SIZE=10;
const dateFormatCache = Object.create(null);
const dateFormatCacheKeys=new Array(DATEFORMAT_CACHE_SIZE);
let dateFormatCacheIndex=0;
function dateFormat(date, format = 'yyyy-MM-dd HH:mm:ss', option){
	if(!format){
		return '';
	}
	const {dateLocale,timezoneOffset}=option||{};
	const locale=dateLocale?Object.assign({},defaultDateLocale,dateLocale):defaultDateLocale;
	//region 校正时区
	if(timezoneOffset!=null&&timezoneOffset!==TIMEZONE_OFFSET_IN_MINUTE){
		date=new Date(date.getTime()-(timezoneOffset-TIMEZONE_OFFSET_IN_MINUTE)*MILLISECONDS_IN_MINUTE);
	}
	//endregion
	const key=format;
	let result='';
	if(!dateFormatCache[key]){
		//region 组装结果并缓存tokens
		const dateFormatTokens=[];
		let match;
		let lastIndex=0;
		let str='';
		while((match=tokenRegex.exec(format))!=null){
			if(match.index!==lastIndex){
				str+=format.slice(lastIndex,match.index);
			}
			lastIndex=tokenRegex.lastIndex;
			const text=match[0];
			if(text in timeFormatter){
				if(str){
					result+=str;
					dateFormatTokens.push({
						type:'text',
						value:str,
					});
					str='';
				}
				result+=timeFormatter[text](date,locale,timezoneOffset);
				dateFormatTokens.push({type:text});
			}else if(text[0]==="'"&&text.length>1){
				str+=replaceString(text.slice(1,text.length-1),"''","'");
			}else{
				str+=text;
			}
		}
		if(lastIndex<format.length){
			str+=format.slice(lastIndex);
		}
		if(str){
			result+=str;
			dateFormatTokens.push({
				type:'text',
				value:str,
			});
		}
		if(dateFormatCacheKeys[dateFormatCacheIndex]!=null){
			delete dateFormatCache[dateFormatCacheKeys[dateFormatCacheIndex]];
		}
		dateFormatCacheKeys[dateFormatCacheIndex]=key;
		dateFormatCache[key]=dateFormatTokens;
		dateFormatCacheIndex=(dateFormatCacheIndex+1)%DATEFORMAT_CACHE_SIZE;
		//endregion
	}else{
		for(const token of dateFormatCache[key]){
			if(token.type==='text'){
				result+=token.value;
			}else{
				result+=timeFormatter[token.type](date,locale,timezoneOffset);
			}
		}
	}
	return result;
}
//endregion
//region dateParse
const dateParseCache = Object.create(null);
const dateParseCacheKeys = new Array(DATEFORMAT_CACHE_SIZE);
let dateParseCacheIndex = 0;
/**
 * 内部解析时间
 * @param dateStr	日期字符串
 * @param format	格式化模板, 可以用 单引号转义, 可以直传正则表达式
 * @param [option]	选项
 * @return 日期对象
 */
function _dateParse(dateStr, format, option){
	const {dateLocale, timezoneOffset} = option || {};
	const locale = dateLocale ? objectAssign({}, defaultDateLocale, dateLocale) : defaultDateLocale;
	const date = {
		year:null,
		month:0,
		day:1,
		hour:0,
		minute:0,
		second:0,
		millisecond:0,
		timezoneOffset:null,
	};
	let match;
	if(format instanceof RegExp){
		match = format.exec(dateStr);
	}else{
		const key = JSON.stringify({
			format,
			dateLocale,
		});
		if(!dateParseCache[key]){
			//region 生成正则表达式和分组信息并缓存
			const quarters = '(' + locale.quarters.join('|') + ')';
			const monthsShort = '(?<MMM>' + locale.monthsShort.join('|') + ')';
			const months = '(?<MMMM>' + locale.months.join('|') + ')';
			const weeksShort = '(' + locale.weeksShort.join('|') + ')';
			const weeks = '(' + locale.weeks.join('|') + ')';
			const am = `(?<a>${locale.am}|${locale.pm})`;
			const regexText = format.replace(tokenRegex, (match) => {
				if(match === 'yy'){
					return '(?<yy>\\d{1,2})';
				}else if(match === 'yyyy'){
					return '(?<yyyy>\\d{1,4})';
				}else if(match === 'Q'){
					return '(\\d)';
				}else if(match === 'QQ'){
					return quarters;
				}else if(match === 'M'){
					return '(?<M>\\d{1,2})';
				}else if(match === 'MM'){
					return '(?<M>\\d{2})';
				}else if(match === 'MMM'){
					return monthsShort;
				}else if(match === 'MMMM'){
					return months;
				}else if(match === 'd'){
					return '(?<d>\\d{1,2})';
				}else if(match === 'dd'){
					return '(?<d>\\d{2})';
				}else if(match === 'D' || match === 'DD'){
					return '(\\d{1,3})';
				}else if(match === 'e' || match === 'E'){
					return '(\\d)';
				}else if(match === 'EE'){
					return weeksShort;
				}else if(match === 'EEE'){
					return weeks;
				}else if(match === 'H'){
					return '(?<H>\\d{1,2})';
				}else if(match === 'HH'){
					return '(?<H>\\d{2})';
				}else if(match === 'h'){
					return '(?<h>\\d{1,2})';
				}else if(match === 'hh'){
					return '(?<h>\\d{2})';
				}else if(match === 'K'){
					return '(?<K>\\d{1,2})';
				}else if(match === 'KK'){
					return '(?<K>\\d{2})';
				}else if(match === 'k'){
					return '(?<k>\\d{1,2})';
				}else if(match === 'kk'){
					return '(?<k>\\d{2})';
				}else if(match === 'm'){
					return '(?<m>\\d{1,2})';
				}else if(match === 'mm'){
					return '(?<m>\\d{2})';
				}else if(match === 's'){
					return '(?<s>\\d{1,2})';
				}else if(match === 'ss'){
					return '(?<s>\\d{2})';
				}else if(match === 'S'){
					return '(?<S>\\d)';
				}else if(match === 'SS'){
					return '(?<S>\\d{2})';
				}else if(match === 'SSS'){
					return '(?<S>\\d{3})';
				}else if(match === 'a'){
					return am;
				}else if(match === 'Z'){
					return '(?<Z>Z|[+-]\\d{2}:\\d{2})';
				}else if(match === 'ZZ'){
					return '(?<ZZ>Z|[+-]\\d{2}\\d{2})';
				}else{
					if(match[0] === '\''){
						match = replaceString(match.slice(1, match.length - 1), '\'\'', '\'');
					}
					return match.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
				}
			});
			if(dateParseCacheKeys[dateParseCacheIndex] != null){
				delete dateParseCache[dateParseCacheKeys[dateParseCacheIndex]];
			}
			dateParseCacheKeys[dateParseCacheIndex] = key;
			dateParseCache[key] = {regex:new RegExp(regexText)};
			dateParseCacheIndex = (dateParseCacheIndex + 1) % DATEFORMAT_CACHE_SIZE;
			//endregion
		}
		const {regex} = dateParseCache[key];
		match = dateStr.match(regex);
	}
	if(match != null && match.groups){
		//region 获取时间信息
		const groups = match.groups;
		if(groups.yyyy){
			date.year = parseInt(groups.yyyy);
		}else if(groups.yy){
			date.year = 2000 + parseInt(groups.yy);
		}
		if(groups.M){
			date.month = parseInt(groups.M) - 1;
		}else if(groups.MMM){
			date.month = locale.monthsShort.indexOf(groups.MMM);
		}else if(groups.MMMM){
			date.month = locale.months.indexOf(groups.MMMM);
		}
		if(groups.d){
			date.day = parseInt(groups.d);
		}
		if(groups.H){
			date.hour = parseInt(groups.H);
		}else if(groups.k){
			date.hour = parseInt(groups.k) % 24;
		}else if(groups.h){
			date.hour = parseInt(groups.h) % 12;
			if(groups.a && groups.a === locale.pm){
				date.hour += 12;
			}
		}else if(groups.K){
			date.hour = parseInt(groups.K);
			if(groups.a && groups.a === locale.pm){
				date.hour += 12;
			}
		}
		if(groups.m){
			date.minute = parseInt(groups.m);
		}
		if(groups.s){
			date.second = parseInt(groups.s);
		}
		if(groups.S){
			const val = groups.S;
			date.millisecond = parseInt(val) * 10 ** (3 - val.length);
		}
		if(timezoneOffset == null && (groups.Z || groups.ZZ)){
			const tzString = (groups.Z ? replaceString(groups.Z, ':', '') : groups.ZZ);
			date.timezoneOffset = (tzString[0] === '+' ? -1 : 1) * (60 * tzString.slice(1, 3) + 1 * tzString.slice(3, 5));
		}
		//endregion
	}else{
		return null;
	}
	const res = new Date(
			date.year,
			date.month,
			date.day,
			date.hour,
			date.minute,
			date.second,
			date.millisecond,
	);
	//region 校正时区
	const offset = timezoneOffset != null ? timezoneOffset : date.timezoneOffset;
	if(offset != null && offset !== TIMEZONE_OFFSET_IN_MINUTE){
		res.setMinutes(res.getMinutes() + offset - TIMEZONE_OFFSET_IN_MINUTE);
	}
	//endregion
	return res;
}
const defaultParseFormat = [
	/(?<yyyy>\d{1,4})(?<sep>[-/.])(?<M>\d{1,2})\k<sep>(?<d>\d{1,2})(?:\s+(?<H>\d{1,2}):(?<m>\d{1,2})(?::(?<s>\d{1,2}))?)?/,
	/(?<yyyy>\d{1,4})年(?<M>\d{1,2})月(?<d>\d{1,2})日\s*(?<H>\d{1,2})时(?<m>\d{1,2})分(?:(?<s>\d{1,2})秒)?/,
	/(?<yyyy>\d{1,4})年(?<M>\d{1,2})月(?<d>\d{1,2})日(?:\s*(?<H>\d{1,2}):(?<m>\d{1,2})(?::(?<s>\d{1,2}))?)?/,
];
/**
 * 解析时间
 * @param dateStr	日期字符串
 * @param formats	格式化模板, 可以用 单引号转义, 可以直传正则表达式(也可以传这些类型的数组, 直到一个解析成功)
 * @param option	选项
 * @return 日期对象
 */
function dateParse(dateStr, formats = defaultParseFormat, option){
	if(Array.isArray(formats)){
		let date = null;
		for(const format of formats){
			date = _dateParse(dateStr, format, option);
			if(date != null){
				return date;
			}
		}
		return date;
	}else{
		return _dateParse(dateStr, formats, option);
	}
}
//endregion
//region getEnvFiles
function checkFile(distPath,arr){
	return stat(distPath).then((stats) => {
		if(stats.isFile()){
			arr.push(distPath);
		}
	}).catch(function(){});
}
async function getEnvFiles({baseDir,envPath,mode}){
	const groupEnvFiles={
		global:[],
		local:[],
		globalMode:[],
		localMode:[],
	};
	const getEnvFiles=(dir) => {
		const dirStr=join(baseDir,dir);
		return stat(dirStr).then((stats) => {
			if(stats.isDirectory()){
				const all=[
					checkFile(join(dirStr,'.env'),groupEnvFiles.global),
					checkFile(join(dirStr,'.env.local'),groupEnvFiles.local),
				];
				if(mode){
					all.push(
						checkFile(join(dirStr,`.env.${mode}`),groupEnvFiles.globalMode),
						checkFile(join(dirStr,`.env.${mode}.local`),groupEnvFiles.localMode),
					)
				}
				return Promise.all(all);
			}
		}).catch(function(){});
	};
	if(Array.isArray(envPath)){
		for(const dir of envPath){
			await getEnvFiles(dir);
		}
	}else{
		await getEnvFiles(envPath);
	}
	return [
		...groupEnvFiles.global,
		...groupEnvFiles.local,
		...groupEnvFiles.globalMode,
		...groupEnvFiles.localMode,
	];
}
//endregion
//region getFileEnvObj
const regexp=/^\s*(\w+)\s*=([^#\n]*)/gm;
function getFileEnvObj(filePath,envObj={}){
	const data=readFileSync(filePath,'utf8');
	let match;
	while(match=regexp.exec(data)){
		let value=match[2].trim();
		if(
			(value[0]==='"'&&value[value.length-1]==='"')
			||
			(value[0]==="'"&&value[value.length-1]==="'")
		){
			value=value.slice(1,-1);
		}
		envObj[match[1]]=value;
	}
	return envObj;
}
//endregion
//region getTerserPlugin
function getTerserPlugin(type,sourceLog,terserOptions){
	let option;
	if(type==='swc'){
		option={
			minify:TerserPlugin.swcMinify,
			extractComments:false,
			terserOptions:extend(terserOptions.swc,{
				compress:{
					drop_console:!sourceLog,
				}
			}),
		};
	}else{
		option={
			extractComments:false,
			terserOptions:extend(terserOptions.default,{
				compress:{
					drop_console:!sourceLog,
				}
			}),
		};
	}
	return new TerserPlugin(option);
}
//endregion
//region 同步获取json
function readJsonSync(url){
	return JSON.parse(readFileSync(url,'utf8'));
}
//endregion

export {
	copyReplace,
	copyCover,
	copyCustom,
	extend,
	dateFormat,
	getEnvFiles,
	getFileEnvObj,
	getTerserPlugin,
	readJsonSync,
};
